﻿using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Utils;
using PMS.Data.Entities.Customers;
using PMS.Data.Entities.Settings;
using PMS.Services.EntityServices.Common;
using PMS.Services.EntityServices.Messages;
using PMS.Services.SharedKernel;
using PMS.Services.Utils;
using PMS.Services.Utils.Events;
using PMS.Services.Utils.Security;
using System;
using System.Text.RegularExpressions;

namespace PMS.Services.EntityServices.Customers
{
    /// <summary>
    /// Customer registration service
    /// </summary>
    public partial class CustomerRegistrationService : ICustomerRegistrationService
    {
        #region Fields

        private readonly IWebHelper _webHelper;
        private readonly IWorkContext _workContext;
        private readonly CustomerSettings _customerSettings;
        private readonly ICustomerService _customerService;
        private readonly IEncryptionService _encryptionService;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly IWorkflowMessageService _workflowMessageService;
        private readonly IGenericAttributeService _genericAttributeService;

        #endregion

        #region Configs
        /// <summary>
        /// Gets a password salt key size
        /// </summary>
        private const int PasswordSaltKeySize = 5;

        /// <summary>
        /// 未开启密码时使用默认密码
        /// </summary>
        private const string DefaultPassword = "888888";
        /// <summary>
        /// Gets a default hash format for customer password
        /// </summary>
        private const string DefaultHashedPasswordFormat = "SHA512";

        #endregion

        #region Ctor

        public CustomerRegistrationService(CustomerSettings customerSettings,
            IWebHelper webHelper,
            IWorkContext workContext,
            ICustomerService customerService,
            IEncryptionService encryptionService,
            IEventDispatcher eventDispatcher,
            IWorkflowMessageService workflowMessageService,
            IGenericAttributeService genericAttributeService)
        {
            _webHelper = webHelper;
            _workContext = workContext;
            _customerSettings = customerSettings;
            _customerService = customerService;
            _encryptionService = encryptionService;
            _eventDispatcher = eventDispatcher;
            _workflowMessageService = workflowMessageService;
            _genericAttributeService = genericAttributeService;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Check whether the entered password matches with a saved one
        /// </summary>
        /// <param name="customerPassword">Customer password</param>
        /// <param name="enteredPassword">The entered password</param>
        /// <returns>True if passwords match; otherwise false</returns>
        protected bool PasswordsMatch(CustomerPassword customerPassword, string enteredPassword)
        {
            if (customerPassword == null || string.IsNullOrEmpty(enteredPassword))
                return false;

            var savedPassword = string.Empty;
            switch (customerPassword.PasswordFormat)
            {
                case PasswordFormat.Clear:
                    savedPassword = enteredPassword;
                    break;
                case PasswordFormat.Encrypted:
                    savedPassword = _encryptionService.EncryptText(enteredPassword);
                    break;
                case PasswordFormat.Hashed:
                    savedPassword = _encryptionService.CreatePasswordHash(enteredPassword, customerPassword.PasswordSalt, _customerSettings.HashedPasswordFormat);
                    break;
            }

            if (customerPassword.Password == null)
                return false;

            return customerPassword.Password.Equals(savedPassword);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Validate customer
        /// </summary>
        /// <param name="loginAccount">Loginname/email/mobile</param>
        /// <param name="password">Password</param>
        /// <returns>Result</returns>
        public virtual (Customer customer, CustomerLoginResults loginResults) ValidateCustomer(string loginAccount, string password)
        {
            Customer customer = null;
            if (_customerSettings.UserRegistrationType == UserRegistrationType.MobileOnly)
                customer = _customerService.GetCustomerByMobile(loginAccount);
            else
            {
                bool maybeMobile = Regex.IsMatch(loginAccount, @"^(1)\d{10}$");
                bool maybeEmail = loginAccount.IndexOf('@', StringComparison.CurrentCultureIgnoreCase) > 0;
                if (maybeMobile)
                    customer = _customerService.GetCustomerByMobile(loginAccount);
                if (maybeEmail && customer == null)
                    customer = _customerService.GetCustomerByEmail(loginAccount);
                if (!maybeEmail && !maybeMobile && customer == null)
                    customer = _customerService.GetCustomerByUsername(loginAccount);
            }

            if (customer == null)
                return (customer,CustomerLoginResults.CustomerNotExist);
            if (customer.IsDeleted) //账号注销
                return (customer, CustomerLoginResults.Deleted);
            if (!customer.IsActive) //账号锁定
                return (customer, CustomerLoginResults.LockedOut);
            //only registered can login
            //账号未激活
            if (!_customerService.IsRegistered(customer))
                return (customer, CustomerLoginResults.NotActive);
            //check whether a customer is locked out
            if (customer.CannotLoginUntilDateUtc.HasValue && customer.CannotLoginUntilDateUtc.Value > DateTime.UtcNow)
                return (customer, CustomerLoginResults.LoginLimitition);

            if (!PasswordsMatch(_customerService.GetCurrentPassword(customer.Id), password))
            {
                //wrong password
                customer.FailedLoginAttempts++;
                if(_customerSettings.TotalFailedAllowedAttemps > 0 && 
                    customer.FailedLoginAttempts >= _customerSettings.TotalFailedAllowedAttemps)
                {
                    //lockout
                    customer.IsActive = false;
                    //reset the counter
                    customer.FailedLoginAttempts = 0;
                    _customerService.UpdateCustomer(customer);
                    return (customer, CustomerLoginResults.LockedOut);
                }
                if (_customerSettings.FailedPasswordAllowedAttempts > 0 &&
                    customer.FailedLoginAttempts >= _customerSettings.FailedPasswordAllowedAttempts)
                {
                    //limit login for a while
                    customer.CannotLoginUntilDateUtc = DateTime.UtcNow.AddMinutes(_customerSettings.FailedPasswordLockoutMinutes);
                    _customerService.UpdateCustomer(customer);
                    return (customer, CustomerLoginResults.LoginLimitition);
                }

                _customerService.UpdateCustomer(customer);

                return (customer, CustomerLoginResults.WrongPassword);
            }

            //update login details
            customer.FailedLoginAttempts = 0;
            customer.CannotLoginUntilDateUtc = null;
            customer.LastLoginTimeUtc = DateTime.UtcNow;
            customer.UpdatedOnTimeUtc = customer.LastActivityTimeUtc = DateTime.UtcNow;
            customer.LastLoginIpAddress = _webHelper.GetCurrentIpAddress();
            _customerService.UpdateCustomer(customer);

            return (customer, CustomerLoginResults.Successful);
        }

        /// <summary>
        /// Register customer
        /// </summary>
        /// <param name="request">Request</param>
        /// <returns>Result</returns>
        public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationRequest request)
        {
            if (request == null)
                throw new ArgumentNullException(nameof(request));

            if (request.Customer == null)
                throw new ArgumentException("Can't load current customer");

            var result = new CustomerRegistrationResult();

            var buildInTaskUser = _customerService.GetOrCreateBackgroundTaskUser();
            if (request.Customer.IsBackgroundBuidInTaskUser() 
              ||request.Customer.LoginName.Equals(buildInTaskUser.LoginName, StringComparison.CurrentCultureIgnoreCase)
              ||request.Customer.Email.Equals(buildInTaskUser.Email, StringComparison.CurrentCultureIgnoreCase)
              ||request.Customer.Mobile.Equals(buildInTaskUser.Mobile, StringComparison.CurrentCultureIgnoreCase))
            {
                result.AddError("Background task account can't be registered");
                return result;
            }
            //validate unique user
            Customer customer = null;
            if (!string.IsNullOrEmpty(request.Customer.Email))
            {
                customer = _customerService.GetCustomerByEmail(request.Customer.Email);
                if(customer != null)
                {
                    result.AddError("Email邮箱已被注册，请更换邮箱重试");
                    return result;
                }
            }
                
            if (!string.IsNullOrEmpty(request.Customer.Mobile))
            {
                customer = _customerService.GetCustomerByMobile(request.Customer.Mobile);
                if (customer != null)
                {
                    result.AddError("手机号码已被注册，请更换注册手机号");
                    return result;
                }
            }

            if (!string.IsNullOrEmpty(request.Customer.LoginName))
            {
                customer = _customerService.GetCustomerByUsername(request.Customer.LoginName);
                if (customer != null)
                {
                    result.AddError("登录账号已被注册，请更换注册登录账号");
                    return result;
                }
            }

            //用户查重校验完成，允许新增用户
            customer = request.Customer;
            _customerService.InsertCustomer(customer);

            var customerPassword = new CustomerPassword
            {
                CustomerId = request.Customer.Id,
                PasswordFormat = request.PasswordFormat,
                PasswordStrongLevel = request.PasswordStrongLevel,
                IsActive = true,
            };
            
            if (string.IsNullOrEmpty(request.Password))
                request.Password = DefaultPassword;
            customerPassword.CreatedOnTimeUtc = customerPassword.UpdatedOnTimeUtc = DateTime.UtcNow;
            switch (request.PasswordFormat)
            {
                case PasswordFormat.Clear:
                    customerPassword.Password = request.Password;
                    break;
                case PasswordFormat.Encrypted:
                    customerPassword.Password = _encryptionService.EncryptText(request.Password);
                    break;
                case PasswordFormat.Hashed:
                    var saltKey = _encryptionService.CreateSaltKey(PasswordSaltKeySize);
                    customerPassword.PasswordSalt = saltKey;
                    customerPassword.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, string.IsNullOrEmpty(_customerSettings.HashedPasswordFormat) ? DefaultHashedPasswordFormat : _customerSettings.HashedPasswordFormat);
                    break;
            }

            _customerService.InsertCustomerPassword(customerPassword);

            Action<CustomerRegistrationRequest> setCustomerRegisted = delegate (CustomerRegistrationRequest customerRegistrationRequest) 
            {
                //add to 'Guest' role
                var registeredRole = _customerService.GetCustomerRoleByRoleType(RoleType.Guests);
                if (registeredRole == null)
                    throw new PMSException("用户注册过程出错：无法分配用户组");
                _customerService.AddCustomerRoleMapping(new CustomerCustomerRoleMapping { CustomerId = customerRegistrationRequest.Customer.Id, CustomerRoleId = registeredRole.Id });
                if (customerRegistrationRequest.Customer.Email.IndexOf("current-store-name.com") <= 0 && customerRegistrationRequest.SendWelcomeMail)
                    _workflowMessageService.SendCustomerRegisteredWelcomeEmail(customerRegistrationRequest.Customer, customerRegistrationRequest.OutputPasswordInEmail ? customerRegistrationRequest.Password : null);
                //raise event       
                _eventDispatcher.Dispatch(new CustomerActivatedEvent(customerRegistrationRequest.Customer));
                //sign in new customer 放弃注册自动登录，返回首页或引导用户自行登录
                //result.JwtToken = _authenticationService.SignIn(customer, false);
            };
            switch (_customerSettings.UserRegistrationType)
            {
                case UserRegistrationType.MobileOnly:
                case UserRegistrationType.Standard:
                    setCustomerRegisted(request);
                    break;
                case UserRegistrationType.EmailValidation:
                    if(request.IsBackendRegister)
                    {//后端管理页注册用户不需要发送注册验证邮件，改为直接注册成功
                    //而是否需要发送欢迎邮件邮参数设置决定
                        setCustomerRegisted(request);
                    } 
                    else
                    {
                        //用户通过注册页注册账号时，由注册设置(customerSettings.UserRegistrationType)
                        //决定是否发送验证邮件
                        _genericAttributeService.SaveAttribute(customer, CustomerDefaultConfigs.AccountActivationTokenAttribute, Guid.NewGuid().ToString());
                        _workflowMessageService.SendCustomerEmailValidationEmail(customer);
                    }
                    break;
                case UserRegistrationType.AdminApproval:
                    //发送管理员用户审核邮件
                    break;
                default:
                    break;
            }
           
            ////add reward points for customer registration (if enabled)
            //if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0)
            //{
            //    var endDate = _rewardPointsSettings.RegistrationPointsValidity > 0
            //        ? (DateTime?)DateTime.UtcNow.AddDays(_rewardPointsSettings.RegistrationPointsValidity.Value) : null;
            //    _rewardPointService.AddRewardPointsHistoryEntry(request.Customer, _rewardPointsSettings.PointsForRegistration,
            //        request.StoreId, _localizationService.GetResource("RewardPoints.Message.EarnedForRegistration"), endDate: endDate);
            //}

            //_customerService.UpdateCustomer(request.Customer);

            return result;
        }

        /// <summary>
        /// Change password
        /// </summary>
        /// <param name="request">Request</param>
        /// <returns>Result</returns>
        public virtual ChangePasswordResult ChangePassword(ChangePasswordRequest request)
        {
            if (request == null)
                throw new ArgumentNullException(nameof(request));
            
            var result = new ChangePasswordResult();
            if (request.CustomerGuid == Guid.Empty)
            {
                result.AddError("", "用户密码修改失败,未找到要修改的用户信息");
                return result;
            }

            if (string.IsNullOrWhiteSpace(request.NewPassword))
            {
                result.AddError(nameof(request.NewPassword), "新修改用户密码不能为空");
                return result;
            }

            var customer = _customerService.GetCustomerByGuid(request.CustomerGuid);
            if (customer == null)
            {
                result.AddError("", "用户密码修改失败:未找到要修改的用户信息");
                return result;
            }

            //request isn't valid
            var customerPassword = _customerService.GetCurrentPassword(customer.Id);
            if (PasswordsMatch(customerPassword, request.NewPassword))
            {
                result.AddError(nameof(request.NewPassword), "新修改密码不能与原密码相同");
                return result;
            }
            if (request.ValidateRequest && !PasswordsMatch(customerPassword, request.OldPassword))
            {
                result.AddError(nameof(request.OldPassword), "用户密码修改失败:原密码验证不正确");
                return result;
            }

            //at this point request is valid
            customerPassword.PasswordStrongLevel = request.PasswordStrong;
            customerPassword.PasswordFormat = request.NewPasswordFormat;
            customerPassword.UpdatedOnTimeUtc = DateTime.UtcNow;
            customerPassword.IsActive = true;
            switch (request.NewPasswordFormat)
            {
                case PasswordFormat.Clear:
                    customerPassword.Password = request.NewPassword;
                    break;
                case PasswordFormat.Encrypted:
                    customerPassword.Password = _encryptionService.EncryptText(request.NewPassword);
                    break;
                case PasswordFormat.Hashed:
                    var saltKey = _encryptionService.CreateSaltKey(PasswordSaltKeySize);
                    customerPassword.PasswordSalt = saltKey;
                    customerPassword.Password = _encryptionService.CreatePasswordHash(request.NewPassword, saltKey,
                        request.HashedPasswordFormat ?? _customerSettings.HashedPasswordFormat);
                    break;
            }

            _customerService.UpdateCustomerPassword(customerPassword);

            //publish event
            _eventDispatcher.Dispatch(new CustomerPasswordChangedEvent(customerPassword));

            return result;
        }

        /// <summary>
        /// Sets a user email
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="newEmail">New email</param>
        /// <param name="requireValidation">Require validation of new email address</param>
        //public virtual void SetEmail(Customer customer, string newEmail, bool requireValidation)
        //{
        //    if (customer == null)
        //        throw new ArgumentNullException(nameof(customer));

        //    if (newEmail == null)
        //        throw new NopException("Email cannot be null");

        //    newEmail = newEmail.Trim();
        //    var oldEmail = customer.Email;

        //    if (!CommonHelper.IsValidEmail(newEmail))
        //        throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.NewEmailIsNotValid"));

        //    if (newEmail.Length > 100)
        //        throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailTooLong"));

        //    var customer2 = _customerService.GetCustomerByEmail(newEmail);
        //    if (customer2 != null && customer.Id != customer2.Id)
        //        throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailAlreadyExists"));

        //    if (requireValidation)
        //    {
        //        //re-validate email
        //        customer.EmailToRevalidate = newEmail;
        //        _customerService.UpdateCustomer(customer);

        //        //email re-validation message
        //        _genericAttributeService.SaveAttribute(customer, NopCustomerDefaults.EmailRevalidationTokenAttribute, Guid.NewGuid().ToString());
        //        _workflowMessageService.SendCustomerEmailRevalidationMessage(customer, _workContext.WorkingLanguage.Id);
        //    }
        //    else
        //    {
        //        customer.Email = newEmail;
        //        _customerService.UpdateCustomer(customer);

        //        if (string.IsNullOrEmpty(oldEmail) || oldEmail.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase))
        //            return;

        //        //update newsletter subscription (if required)
        //        foreach (var store in _storeService.GetAllStores())
        //        {
        //            var subscriptionOld = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId(oldEmail, store.Id);

        //            if (subscriptionOld == null)
        //                continue;

        //            subscriptionOld.Email = newEmail;
        //            _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscriptionOld);
        //        }
        //    }
        //}

        /// <summary>
        /// Sets a customer username
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="newUsername">New Username</param>
        //public virtual void SetUsername(Customer customer, string newUsername)
        //{
        //    if (customer == null)
        //        throw new ArgumentNullException(nameof(customer));

        //    if (!_customerSettings.UsernamesEnabled)
        //        throw new NopException("Usernames are disabled");

        //    newUsername = newUsername.Trim();

        //    if (newUsername.Length > NopCustomerServicesDefaults.CustomerUsernameLength)
        //        throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameTooLong"));

        //    var user2 = _customerService.GetCustomerByUsername(newUsername);
        //    if (user2 != null && customer.Id != user2.Id)
        //        throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameAlreadyExists"));

        //    customer.Username = newUsername;
        //    _customerService.UpdateCustomer(customer);
        //}

        #endregion
    }
}
